﻿using System.Drawing;
using System.Globalization;
using OnDash.Mathematics.Vector;

namespace OnDash.Mathematics.Data;

public struct Color4(float red, float green, float blue, float alpha)
    : IEquatable<Color4>
{
    public float R = red;
    public float G = green;
    public float B = blue;
    public float A = alpha;

    public Color4(byte red, byte green, byte blue, byte alpha) : this(red / (float)byte.MaxValue,
        green / (float)byte.MaxValue, blue / (float)byte.MaxValue, alpha / (float)byte.MaxValue)
    {
    }

    public int ToArgb()
    {
        var value = (uint)(A * byte.MaxValue) << 24 |
                    (uint)(R * byte.MaxValue) << 16 |
                    (uint)(G * byte.MaxValue) << 8 |
                    (uint)(B * byte.MaxValue);

        return unchecked((int)value);
    }

    public static Color4 FromSrgb(Color4 srgb)
    {
        float r, g, b;

        if (srgb.R <= 0.04045f) r = srgb.R / 12.92f;
        else r = MathHelper.Pow((srgb.R + 0.055f) / (1 + 0.055f), 2.4f);

        if (srgb.G <= 0.055f) g = srgb.G / 12.92f;
        else g = MathHelper.Pow((srgb.G + 0.055f) / (1 + 0.055f), 2.4f);

        if (srgb.B <= 0.039f) b = srgb.B / 12.92f;
        else b = MathHelper.Pow((srgb.B + 0.055f) / (1 + 0.055f), 2.4f);

        return new Color4(r, g, b, srgb.A);
    }

    public static Color4 ToSrgb(Color4 rgb)
    {
        float r, g, b;

        if (rgb.R <= 0.0031308f) r = rgb.R * 12.92f;
        else r = (1 - 0.055f) * MathHelper.Pow(rgb.R, 1 / 2.4f) - 0.055f;

        if (rgb.G <= 0.00313068f) g = rgb.G * 12.92f;
        else g = (1 - 0.055f) * MathHelper.Pow(rgb.G, 1 / 2.4f) - 0.055f;

        if (rgb.B <= 0.00313068f) b = rgb.B * 12.92f;
        else b = (1 - 0.055f) * MathHelper.Pow(rgb.B, 1 / 2.4f) - 0.055f;

        return new Color4(r, g, b, rgb.A);
    }

    public static Color4 FromHsl(Vector4 hsl)
    {
        var hue = hsl.X * 360;
        var saturation = hsl.Y;
        var lightness = hsl.Z;

        var c = 1 - MathHelper.Abs(2 * lightness - 1) * saturation;
        var h = hue / 60;
        var x = c * (1 - MathHelper.Abs(h % 2 - 1));

        float r, g, b;
        switch (h)
        {
            case >= 0f and < 1f:
                r = c;
                g = x;
                b = 0f;
                break;
            case >= 1f and < 2f:
                r = x;
                g = c;
                b = 0f;
                break;
            case >= 2f and < 3f:
                r = 0f;
                g = c;
                b = x;
                break;
            case >= 3f and < 4f:
                r = 0f;
                g = x;
                b = c;
                break;
            case >= 4f and < 5f:
                r = x;
                g = 0f;
                b = c;
                break;
            case >= 5f and < 6f:
                r = c;
                g = 0f;
                b = x;
                break;
            default:
                r = 0f;
                g = 0f;
                b = 0f;
                break;
        }

        var m = lightness - c * 0.5f;
        return new Color4(r + m, g + m, b + m, hsl.W);
    }

    public static Vector4 ToHsl(Color4 rgb)
    {
        const float tolerance = 0.001f;
        var max = MathHelper.Max(rgb.R, MathHelper.Max(rgb.G, rgb.B));
        var min = MathHelper.Min(rgb.R, MathHelper.Min(rgb.G, rgb.B));
        var c = max - min;

        float h = 0;
        if (MathHelper.Abs(max - rgb.R) < tolerance) h = (rgb.G - rgb.B) / c;
        else if (MathHelper.Abs(max - rgb.G) < tolerance) h = (rgb.B - rgb.R) / c + 2f;
        else if (MathHelper.Abs(max - rgb.B) < tolerance) h = (rgb.R - rgb.G) / c + 4f;

        var hue = h / 6;
        if (hue < 0) hue += 1;

        var lightness = (max + min) / 2;
        var saturation = 0f;

        if (0f != lightness && MathHelper.Abs(lightness - 1f) > tolerance)
            saturation = c / (1f - MathHelper.Abs(2f * lightness - 1f));

        return new Vector4(hue, saturation, lightness, rgb.A);
    }

    public static Color4 FromHsv(Vector4 hsv)
    {
        var hue = hsv.X * 360f;
        var saturation = hsv.Y;
        var value = hsv.Z;

        var c = value * saturation;

        var h = hue / 60f;
        var x = c * (1f - MathHelper.Abs(h % 2f - 1f));

        float r, g, b;
        switch (h)
        {
            case >= 0f and < 1f:
                r = c;
                g = x;
                b = 0f;
                break;
            case >= 1f and < 2f:
                r = x;
                g = c;
                b = 0f;
                break;
            case >= 2f and < 3f:
                r = 0f;
                g = c;
                b = x;
                break;
            case >= 3f and < 4f:
                r = 0f;
                g = x;
                b = c;
                break;
            case >= 4f and < 5f:
                r = x;
                g = 0f;
                b = c;
                break;
            case >= 5f and < 6f:
                r = c;
                g = 0f;
                b = x;
                break;
            default:
                r = 0f;
                g = 0f;
                b = 0f;
                break;
        }

        var m = value - c;
        return new Color4(r + m, g + m, b + m, hsv.W);
    }

    public static Vector4 ToHsv(Color4 rgb)
    {
        const float tolerance = 0.001f;

        var max = MathHelper.Max(rgb.R, MathHelper.Max(rgb.G, rgb.B));
        var min = MathHelper.Min(rgb.R, MathHelper.Min(rgb.G, rgb.B));

        var c = max - min;
        var h = 0f;
        if (MathHelper.Abs(max - rgb.R) < tolerance)
            h = (rgb.G - rgb.B) / c % 6.0f;
        else if (MathHelper.Abs(max - rgb.G) < tolerance)
            h = (rgb.B - rgb.R) / c + 2.0f;
        else if (MathHelper.Abs(max - rgb.B) < tolerance)
            h = (rgb.R - rgb.G) / c + 4.0f;

        var hue = h * 60.0f / 360.0f;

        var saturation = 0.0f;
        if (0.0f != max) saturation = c / max;

        return new Vector4(hue, saturation, max, rgb.A);
    }

    public static Color4 FromXyz(Vector4 xyz)
    {
        var r = 0.41847f * xyz.X + -0.15866f * xyz.Y + -0.082835f * xyz.Z;
        var g = -0.091169f * xyz.X + 0.25243f * xyz.Y + 0.015708f * xyz.Z;
        var b = 0.00092090f * xyz.X + -0.0025498f * xyz.Y + 0.17860f * xyz.Z;
        return new Color4(r, g, b, xyz.W);
    }

    public static Vector4 ToXyz(Color4 rgb)
    {
        var x = (0.49f * rgb.R + 0.31f * rgb.G + 0.20f * rgb.B) / 0.17697f;
        var y = (0.17697f * rgb.R + 0.81240f * rgb.G + 0.01063f * rgb.B) / 0.17697f;
        var z = (0.00f * rgb.R + 0.01f * rgb.G + 0.99f * rgb.B) / 0.17697f;
        return new Vector4(x, y, z, rgb.A);
    }

    public static Color4 FromYcbcr(Vector4 ycbcr)
    {
        var r = 1.0f * ycbcr.X + 0.0f * ycbcr.Y + 1.402f * ycbcr.Z;
        var g = 1.0f * ycbcr.X + -0.344136f * ycbcr.Y + -0.714136f * ycbcr.Z;
        var b = 1.0f * ycbcr.X + 1.772f * ycbcr.Y + 0.0f * ycbcr.Z;
        return new Color4(r, g, b, ycbcr.W);
    }

    public static Vector4 ToYcbcr(Color4 rgb)
    {
        var y = 0.299f * rgb.R + 0.587f * rgb.G + 0.114f * rgb.B;
        var u = -0.168736f * rgb.R + -0.331264f * rgb.G + 0.5f * rgb.B;
        var v = 0.5f * rgb.R + -0.418688f * rgb.G + -0.081312f * rgb.B;
        return new Vector4(y, u, v, rgb.A);
    }

    public static Color4 FromHcy(Vector4 hcy)
    {
        var hue = hcy.X * 360f;
        var c = hcy.Y;
        var luminance = hcy.Z;

        var h = hue / 60f;
        var x = c * (1f - MathHelper.Abs(h % 2f - 1f));

        float r, g, b;
        switch (h)
        {
            case >= 0f and < 1f:
                r = c;
                g = x;
                b = 0f;
                break;
            case >= 1f and < 2f:
                r = x;
                g = c;
                b = 0f;
                break;
            case >= 2f and < 3f:
                r = 0f;
                g = c;
                b = x;
                break;
            case >= 3f and < 4f:
                r = 0f;
                g = x;
                b = c;
                break;
            case >= 4f and < 5f:
                r = x;
                g = 0f;
                b = c;
                break;
            case >= 5f and < 6f:
                r = c;
                g = 0f;
                b = x;
                break;
            default:
                r = 0f;
                g = 0f;
                b = 0f;
                break;
        }

        var m = luminance - (0.3f * r + 0.59f * g + 0.11f * b);
        return new Color4(r + m, g + m, b + m, hcy.W);
    }

    public static Vector4 ToHcy(Color4 rgb)
    {
        const float tolerance = 0.001f;
        var max = MathHelper.Max(rgb.R, MathHelper.Max(rgb.G, rgb.B));
        var min = MathHelper.Min(rgb.R, MathHelper.Min(rgb.G, rgb.B));
        var c = max - min;

        var h = 0f;
        if (MathHelper.Abs(max - rgb.R) < tolerance)
            h = (rgb.G - rgb.B) / c % 6f;
        else if (MathHelper.Abs(max - rgb.G) < tolerance)
            h = (rgb.B - rgb.R) / c + 2f;
        else if (MathHelper.Abs(max - rgb.B) < tolerance)
            h = (rgb.R - rgb.G) / c + 4f;

        var hue = (h * 60f) / 360f;
        var luminance = 0.3f * rgb.R + 0.59f * rgb.G + 0.11f * rgb.B;

        return new Vector4(hue, c, luminance, rgb.A);
    }

    public static Color4 Transparent => new(255, 255, 255, 0);
    public static Color4 AliceBlue => new(240, 248, 255, 255);
    public static Color4 AntiqueWhite => new(250, 235, 215, 255);
    public static Color4 Aqua => new(0, 255, 255, 255);
    public static Color4 Aquamarine => new(127, 255, 212, 255);
    public static Color4 Azure => new(240, 255, 255, 255);
    public static Color4 Beige => new(245, 245, 220, 255);
    public static Color4 Bisque => new(255, 228, 196, 255);
    public static Color4 Black => new(0, 0, 0, 255);
    public static Color4 BlanchedAlmond => new(255, 235, 205, 255);
    public static Color4 Blue => new(0, 0, 255, 255);
    public static Color4 BlueViolet => new(138, 43, 226, 255);
    public static Color4 Brown => new(165, 42, 42, 255);
    public static Color4 BurlyWood => new(222, 184, 135, 255);
    public static Color4 CadetBlue => new(95, 158, 160, 255);
    public static Color4 Chartreuse => new(127, 255, 0, 255);
    public static Color4 Chocolate => new(210, 105, 30, 255);
    public static Color4 Coral => new(255, 127, 80, 255);
    public static Color4 CornflowerBlue => new(100, 149, 237, 255);
    public static Color4 Cornsilk => new(255, 248, 220, 255);
    public static Color4 Crimson => new(220, 20, 60, 255);
    public static Color4 Cyan => new(0, 255, 255, 255);
    public static Color4 DarkBlue => new(0, 0, 139, 255);
    public static Color4 DarkCyan => new(0, 139, 139, 255);
    public static Color4 DarkGoldenrod => new(184, 134, 11, 255);
    public static Color4 DarkGray => new(169, 169, 169, 255);
    public static Color4 DarkGreen => new(0, 100, 0, 255);
    public static Color4 DarkKhaki => new(189, 183, 107, 255);
    public static Color4 DarkMagenta => new(139, 0, 139, 255);
    public static Color4 DarkOliveGreen => new(85, 107, 47, 255);
    public static Color4 DarkOrange => new(255, 140, 0, 255);
    public static Color4 DarkOrchid => new(153, 50, 204, 255);
    public static Color4 DarkRed => new(139, 0, 0, 255);
    public static Color4 DarkSalmon => new(233, 150, 122, 255);
    public static Color4 DarkSeaGreen => new(143, 188, 139, 255);
    public static Color4 DarkSlateBlue => new(72, 61, 139, 255);
    public static Color4 DarkSlateGray => new(47, 79, 79, 255);
    public static Color4 DarkTurquoise => new(0, 206, 209, 255);
    public static Color4 DarkViolet => new(148, 0, 211, 255);
    public static Color4 DeepPink => new(255, 20, 147, 255);
    public static Color4 DeepSkyBlue => new(0, 191, 255, 255);
    public static Color4 DimGray => new(105, 105, 105, 255);
    public static Color4 DodgerBlue => new(30, 144, 255, 255);
    public static Color4 Firebrick => new(178, 34, 34, 255);
    public static Color4 FloralWhite => new(255, 250, 240, 255);
    public static Color4 ForestGreen => new(34, 139, 34, 255);
    public static Color4 Fuchsia => new(255, 0, 255, 255);
    public static Color4 Gainsboro => new(220, 220, 220, 255);
    public static Color4 GhostWhite => new(248, 248, 255, 255);
    public static Color4 Gold => new(255, 215, 0, 255);
    public static Color4 Goldenrod => new(218, 165, 32, 255);
    public static Color4 Gray => new(128, 128, 128, 255);
    public static Color4 Green => new(0, 128, 0, 255);
    public static Color4 GreenYellow => new(173, 255, 47, 255);
    public static Color4 Honeydew => new(240, 255, 240, 255);
    public static Color4 HotPink => new(255, 105, 180, 255);
    public static Color4 IndianRed => new(205, 92, 92, 255);
    public static Color4 Indigo => new(75, 0, 130, 255);
    public static Color4 Ivory => new(255, 255, 240, 255);
    public static Color4 Khaki => new(240, 230, 140, 255);
    public static Color4 Lavender => new(230, 230, 250, 255);
    public static Color4 LavenderBlush => new(255, 240, 245, 255);
    public static Color4 LawnGreen => new(124, 252, 0, 255);
    public static Color4 LemonChiffon => new(255, 250, 205, 255);
    public static Color4 LightBlue => new(173, 216, 230, 255);
    public static Color4 LightCoral => new(240, 128, 128, 255);
    public static Color4 LightCyan => new(224, 255, 255, 255);
    public static Color4 LightGoldenrodYellow => new(250, 250, 210, 255);
    public static Color4 LightGreen => new(144, 238, 144, 255);
    public static Color4 LightGray => new(211, 211, 211, 255);
    public static Color4 LightPink => new(255, 182, 193, 255);
    public static Color4 LightSalmon => new(255, 160, 122, 255);
    public static Color4 LightSeaGreen => new(32, 178, 170, 255);
    public static Color4 LightSkyBlue => new(135, 206, 250, 255);
    public static Color4 LightSlateGray => new(119, 136, 153, 255);
    public static Color4 LightSteelBlue => new(176, 196, 222, 255);
    public static Color4 LightYellow => new(255, 255, 224, 255);
    public static Color4 Lime => new(0, 255, 0, 255);
    public static Color4 LimeGreen => new(50, 205, 50, 255);
    public static Color4 Linen => new(250, 240, 230, 255);
    public static Color4 Magenta => new(255, 0, 255, 255);
    public static Color4 Maroon => new(128, 0, 0, 255);
    public static Color4 MediumAquamarine => new(102, 205, 170, 255);
    public static Color4 MediumBlue => new(0, 0, 205, 255);
    public static Color4 MediumOrchid => new(186, 85, 211, 255);
    public static Color4 MediumPurple => new(147, 112, 219, 255);
    public static Color4 MediumSeaGreen => new(60, 179, 113, 255);
    public static Color4 MediumSlateBlue => new(123, 104, 238, 255);
    public static Color4 MediumSpringGreen => new(0, 250, 154, 255);
    public static Color4 MediumTurquoise => new(72, 209, 204, 255);
    public static Color4 MediumVioletRed => new(199, 21, 133, 255);
    public static Color4 MidnightBlue => new(25, 25, 112, 255);
    public static Color4 MintCream => new(245, 255, 250, 255);
    public static Color4 MistyRose => new(255, 228, 225, 255);
    public static Color4 Moccasin => new(255, 228, 181, 255);
    public static Color4 NavajoWhite => new(255, 222, 173, 255);
    public static Color4 Navy => new(0, 0, 128, 255);
    public static Color4 OldLace => new(253, 245, 230, 255);
    public static Color4 Olive => new(128, 128, 0, 255);
    public static Color4 OliveDrab => new(107, 142, 35, 255);
    public static Color4 Orange => new(255, 165, 0, 255);
    public static Color4 OrangeRed => new(255, 69, 0, 255);
    public static Color4 Orchid => new(218, 112, 214, 255);
    public static Color4 PaleGoldenrod => new(238, 232, 170, 255);
    public static Color4 PaleGreen => new(152, 251, 152, 255);
    public static Color4 PaleTurquoise => new(175, 238, 238, 255);
    public static Color4 PaleVioletRed => new(219, 112, 147, 255);
    public static Color4 PapayaWhip => new(255, 239, 213, 255);
    public static Color4 PeachPuff => new(255, 218, 185, 255);
    public static Color4 Peru => new(205, 133, 63, 255);
    public static Color4 Pink => new(255, 192, 203, 255);
    public static Color4 Plum => new(221, 160, 221, 255);
    public static Color4 PowderBlue => new(176, 224, 230, 255);
    public static Color4 Purple => new(128, 0, 128, 255);
    public static Color4 Red => new(255, 0, 0, 255);
    public static Color4 RosyBrown => new(188, 143, 143, 255);
    public static Color4 RoyalBlue => new(65, 105, 225, 255);
    public static Color4 SaddleBrown => new(139, 69, 19, 255);
    public static Color4 Salmon => new(250, 128, 114, 255);
    public static Color4 SandyBrown => new(244, 164, 96, 255);
    public static Color4 SeaGreen => new(46, 139, 87, 255);
    public static Color4 SeaShell => new(255, 245, 238, 255);
    public static Color4 Sienna => new(160, 82, 45, 255);
    public static Color4 Silver => new(192, 192, 192, 255);
    public static Color4 SkyBlue => new(135, 206, 235, 255);
    public static Color4 SlateBlue => new(106, 90, 205, 255);
    public static Color4 SlateGray => new(112, 128, 144, 255);
    public static Color4 Snow => new(255, 250, 250, 255);
    public static Color4 SpringGreen => new(0, 255, 127, 255);
    public static Color4 SteelBlue => new(70, 130, 180, 255);
    public static Color4 Tan => new(210, 180, 140, 255);
    public static Color4 Teal => new(0, 128, 128, 255);
    public static Color4 Thistle => new(216, 191, 216, 255);
    public static Color4 Tomato => new(255, 99, 71, 255);
    public static Color4 Turquoise => new(64, 224, 208, 255);
    public static Color4 Violet => new(238, 130, 238, 255);
    public static Color4 Wheat => new(245, 222, 179, 255);
    public static Color4 White => new(255, 255, 255, 255);
    public static Color4 WhiteSmoke => new(245, 245, 245, 255);
    public static Color4 Yellow => new(255, 255, 0, 255);
    public static Color4 YellowGreen => new(154, 205, 50, 255);

    public static implicit operator Color4(Color color) => new(color.R, color.G, color.B, color.A);

    public static explicit operator Color(Color4 color)
    {
        return Color.FromArgb((int)(color.A * byte.MaxValue),
            (int)(color.R * byte.MaxValue),
            (int)(color.G * byte.MaxValue),
            (int)(color.B * byte.MaxValue));
    }

    public static bool operator ==(Color4 left, Color4 right) => left.Equals(right);
    public static bool operator !=(Color4 left, Color4 right) => !left.Equals(right);

    public override string ToString()
    {
        return string.Format(
            "(RGBA) = ({0}, {1}, {2}, {3})", R.ToString(CultureInfo.InvariantCulture),
            G.ToString(CultureInfo.InvariantCulture),
            B.ToString(CultureInfo.InvariantCulture),
            A.ToString(CultureInfo.InvariantCulture)
        );
    }

    public bool Equals(Color4 other) => R.Equals(other.R) &&
                                        G.Equals(other.G) &&
                                        B.Equals(other.B) &&
                                        A.Equals(other.A);

    public override bool Equals(object? obj) => obj is Color4 other && Equals(other);

    public override int GetHashCode() => ToArgb();
}